using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading;

using LuaInterface;

using libsecondlife;

using Feathertail;
using Feathertail.Lua;

namespace GliderLua
{
    class GliderLuaApp
    {
        /// <summary>
        /// Main libsl interface
        /// </summary>
        protected FeathertailClient _Client;

        /// <summary>
        /// Used to indicate if we're currently connected
        /// </summary>
        protected ManualResetEvent _ConnectedSignal = new ManualResetEvent(false);

        /// <summary>
        /// Lua Player
        /// </summary>
        protected Lua _LuaVM = new Lua();
        protected Dictionary<string, LuaFuncDescriptor> _LuaFuncs = new Dictionary<string, LuaFuncDescriptor>();


        /// <summary>
        /// Used to flag when we should exit
        /// </summary>
        protected bool _KeepRunning = true;

        /// <summary>
        /// Duh
        /// </summary>
        /// <param IncomingName="args"></param>
        static void Main(string[] args)
        {

            if (args.Length < 3)
            {
                Console.Write("First Name: ");
                String first = Console.ReadLine();

                Console.Write("Last Name: ");
                String last = Console.ReadLine();

                Console.Write("Password: ");
                String pass = Console.ReadLine();

                args = new string[] { first, last, pass };
            }

            GliderLuaApp glider = new GliderLuaApp();

            // Register base LUA commands from the client
            glider.RegisterLuaFunctions(glider, Console.Out);


            // Load commands
            List<string> LoadErrors = new List<string>();

            // Load default commands shipped with glider.
            LoadErrors.AddRange(glider.LoadCommands("NestBox.dll", Console.Out));

            // If we had any errors, display them and ask to continue
            if (LoadErrors.Count > 0)
            {
                Console.WriteLine("The following errors occured while loading commands: ");
                foreach (string Error in LoadErrors)
                {
                    Console.WriteLine(Error);
                }

                Console.Write("Do you wish to continue? Y/[N]: ");
                if (!Console.ReadLine().ToLower().Equals("y"))
                {
                    Console.WriteLine("Exiting...");
                    return;
                }
            }

            if (glider.Connect(args[0], args[1], args[2]))
            {
                if (glider._ConnectedSignal.WaitOne(TimeSpan.FromMinutes(1), false))
                {
                    glider.CommandParser();
                    glider.Disconnect();
                }
                else
                {
                    Console.WriteLine("Timed out while waiting to login.");
                }
            }
        }

        /// <summary>
        /// Default Constructor
        /// </summary>
        protected GliderLuaApp()
        {
            try
            {
                _Client = new FeathertailClient();
                _Client.Network.OnConnected += new NetworkManager.ConnectedCallback(Network_OnConnected);
            }
            catch (Exception e)
            {
                // Error initializing the client
                Console.WriteLine();
                Console.WriteLine(e.ToString());
            }

        }

        protected void CommandParser()
        {
            string strInput;
            while (_KeepRunning)
            {
                Console.Write("> ");

                strInput = Console.ReadLine();
                Console.WriteLine();
                try
                {
                    _LuaVM.DoString(strInput);
                }

                catch (Exception ex)
                {
                    Console.WriteLine("ex:" + ex.Message);
                }

                finally
                {
                    Console.WriteLine();
                }

            }
        }



        #region Client Connection/Disconnection

        /// <summary>
        /// Set the connected signal, once we're connected.
        /// </summary>
        /// <param IncomingName="sender"></param>
        protected void Network_OnConnected(object sender)
        {
            _ConnectedSignal.Set();

            _Client.Self.Status.FinishAnim = true;
            _Client.Self.Status.SendUpdate();

            _Client.Self.Status.StandUp = true;
            _Client.Self.Status.SendUpdate();

            _Client.Self.Status.FinishAnim = false;
            _Client.Self.Status.StandUp = false;

            _Client.Self.Status.UpdateTimer.Start();

            _Client.Appearance.BeginAgentSendAppearance();
        }

        /// <summary>
        /// Attempt to connect to SL
        /// </summary>
        /// <param IncomingName="FirstName"></param>
        /// <param IncomingName="LastName"></param>
        /// <param IncomingName="Password"></param>
        /// <returns></returns>
        protected bool Connect(string FirstName, string LastName, string Password)
        {
            Console.WriteLine("Attempting to connect and login to SecondLife.");

            // Setup Login to Second Life
            Dictionary<string, object> loginReply = new Dictionary<string, object>();

            // Login
            if (!_Client.Network.Login(FirstName, LastName, Password, "GliderCLI", "static.sprocket@gmail.com"))
            {
                // Login failed
                Console.WriteLine("Error logging in: " + _Client.Network.LoginErrorKey + " : " + _Client.Network.LoginMessage);
                return false;
            }

            // Login was successful
            Console.WriteLine("Login was successful.");
            Console.WriteLine("AgentID:   " + _Client.Network.AgentID);
            Console.WriteLine("SessionID: " + _Client.Network.SessionID);

            return true;
        }

        /// <summary>
        /// Attempt to disconnect from SL
        /// </summary>
        protected void Disconnect()
        {
            // Logout of Second Life
            Console.WriteLine("Request logout");
            _Client.Network.Logout();

            // Just a hack until we have a better way of waiting for logout, like a ManualEvent
            Thread.Sleep(3000);
        }


        #endregion

        #region Lua Commands
        [AttrLuaFunc("quit", "Exit the program.")]
        public void quit()
        {
            _KeepRunning = false;
        }


        [AttrLuaFunc("help", "List available commands.")]
        public void help()
        {
            Console.WriteLine("Available commands: ");
            Console.WriteLine();

            foreach (KeyValuePair<string, LuaFuncDescriptor> kvp in _LuaFuncs)
            {
                Console.WriteLine(kvp.Value.getFuncHeader());
            }
        }

        [AttrLuaFunc("helpcmd", "Show help for a given command", "Command to get help of.")]
        public void help(String strCmd)
        {
            if (!_LuaFuncs.ContainsKey(strCmd))
            {
                Console.WriteLine("No such function or package: " + strCmd);
                return;
            }

            LuaFuncDescriptor pDesc = (LuaFuncDescriptor)_LuaFuncs[strCmd];
            Console.WriteLine(pDesc.getFuncFullDoc());
        }
        #endregion

        #region Lua Command Registration
        /// <summary>
        /// Loads commands
        /// </summary>
        /// <returns>Any error messages</returns>
        protected List<string> LoadCommands(string AssemblyDLL, TextWriter LogWriter)
        {
            LogWriter.WriteLine("Loading Commands from: " + AssemblyDLL);

            List<string> Errors = new List<string>();

            // List all the types in the assembly that is passed in as a parameter
            Assembly LoadedAssembly = Assembly.LoadFrom(AssemblyDLL);

            // Walk through each Type and list their Information
            foreach (Type type in LoadedAssembly.GetTypes())
            {
                if (type.GetInterface("Feathertail.Command") != null)
                {
                    LogWriter.WriteLine("Loading " + type.FullName + "...");

                    Command cmd = (Command)Activator.CreateInstance(type);

                    // Add to command dictionary
                    RegisterLuaFunctions(cmd, LogWriter);

                    // Do any setup this command requires.
                    cmd.Setup(_Client);
                }
            }

            return Errors;

        }
        protected void RegisterLuaFunctions(object oTarget, TextWriter LogWriter)
        {
            // Sanity checks
            if (_LuaVM == null || _LuaFuncs == null)
                return;

            // Get the target type
            Type pTrgType = oTarget.GetType();

            // ... and simply iterate through all it's methods
            foreach (MethodInfo mInfo in pTrgType.GetMethods())
            {
                // ... then through all this method's attributes
                foreach (Attribute attr in Attribute.GetCustomAttributes(mInfo))
                {
                    Console.WriteLine(attr.ToString());

                    // and if they happen to be one of our AttrLuaFunc attributes
                    if (attr.GetType() == typeof(AttrLuaFunc))
                    {
                        AttrLuaFunc pAttr = (AttrLuaFunc)attr;
                        Dictionary<string, string> Params = new Dictionary<string,string>();

                        // Get the desired function name and doc string, along with parameter info
                        String strFName = pAttr.getFuncName();
                        String strFDoc = pAttr.getFuncDoc();
                        String[] pPrmDocs = pAttr.getFuncParams();

                        // Now get the expected parameters from the MethodInfo object
                        ParameterInfo[] pPrmInfo = mInfo.GetParameters();

                        // If they don't match, someone forgot to add some documentation to the 
                        // attribute, complain and go to the next method
                        if (pPrmDocs != null && (pPrmInfo.Length != pPrmDocs.Length))
                        {
                            Console.WriteLine("Function " + mInfo.Name + " (exported as " +
                                              strFName + ") argument number mismatch. Declared " +
                                              pPrmDocs.Length + " but requires " +
                                              pPrmInfo.Length + ".");
                            break;
                        }

                        // Build a parameter <-> parameter doc hashtable
                        for (int i = 0; i < pPrmInfo.Length; i++)
                        {
                            Params.Add(pPrmInfo[i].Name, pPrmDocs[i]);
                        }

                        // Get a new function descriptor from this information
                        LuaFuncDescriptor pDesc = new LuaFuncDescriptor(strFName, strFDoc, Params);

                        // Add it to the global hashtable
                        _LuaFuncs.Add(strFName, pDesc);
                        LogWriter.WriteLine("\t" + strFName);

                        // And tell the VM to register it.
                        _LuaVM.RegisterFunction(strFName, oTarget, mInfo);
                    }
                }
            }
        }
        #endregion
    }
}
